home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Languguage OS 2
/
Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO
/
language
/
parallax
/
more_exa.tar
/
more
/
Graphics
/
zbuff.p
< prev
Wrap
Text File
|
1992-01-20
|
30KB
|
946 lines
(***************************************************************************)
(* *)
(* Pixelparallel Z-Buffer-Algorithm *)
(* *)
(* *)
(* Author : Sabine Liebelt *)
(* File : zbuff.p *)
(* Language : Parallaxis *)
(* *)
(***************************************************************************)
SYSTEM zbuff;
CONST
Size = 200; (* screensize in pixel *)
MaxPoly = 200; (* Max. number of polygon *)
MaxEdges = 5; (* Max. number of corners per poly.*)
MaxLights = 5; (* max. number of lights *)
Eps = 0.0001; (* Ungenauigkeitsfaktor *)
TYPE
string = ARRAY [1..50] OF CHAR;
Vec = ARRAY [1..3] OF REAL; (* coefficients of one corner *)
RGB = ARRAY [1..3] OF REAL; (* colorvec *)
Pixel = RECORD (* per pixel: *)
x,y : REAL; (* coordinates, (0,0) in the center*)
z: REAL; (* z-value of visible poly. *)
color: RGB; (* color of visible poly. *)
END (* record *);
PixGrid = ARRAY [1..Size],[1..Size] OF Pixel;
Polygon = RECORD
pid: CARDINAL; (* unique polynumber *)
a,b,c,d: REAL; (* coeffic. of plain, describing *)
(* by the poly *)
vertices: ARRAY [1..MaxEdges] OF Vec;
(* list of polycorners *)
vcount: INTEGER; (* count corners *)
color: RGB; (* colorvalue poly *)
END (* record *);
PolyList = ARRAY [1..MaxPoly] OF Polygon;
(* table of polygones *)
Viewer = RECORD
pos: Vec; (* point of view *)
at: Vec; (* viewing direction *)
up: Vec; (* direction above *)
angle: REAL; (* view-angle in degree *)
END;
Light = RECORD
pos: Vec;
intensity: RGB;
END;
LightList = ARRAY [1..MaxLights] OF Light;
CONFIGURATION
grid [1..Size], [1..Size];
CONNECTION;
SCALAR
sin_x, cos_x: REAL; (* sinus, cosinus of rotary angle*)
(* around x - axle *)
sin_y, cos_y: REAL; (* sinus, cosinus of rotary angle*)
(* around y - axle *)
sin_z, cos_z: REAL; (* sinus, cosinus of rotary angle*)
(* around z - axle *)
up: Vec; (* transformated up-vevtor *)
interval: REAL; (* expansion of pixel *)
eye: Viewer; (* viewer-data *)
lightcount: INTEGER; (* lightcounter *)
ll: LightList; (* lightlist *)
pl: PolyList; (* polygone-table *)
pg: PixGrid; (* array for whole imagesize *)
polycount: INTEGER; (* counter for polygone *)
i,j: INTEGER; (* counter *)
hither: REAL;
amb: RGB; (* ambient light *)
background: RGB; (* backgroundcolor *)
objfile, picfile, inputf: string; (* inputfile *)
VECTOR
vpixel: Pixel;
Vecpoly: Polygon;
PROCEDURE strcat( SCALAR first , second : string ) : SCALAR string ;
(***************************************************************************)
(* Concatenates two strings *)
(***************************************************************************)
SCALAR
i , j : INTEGER;
BEGIN
i := 1 ; j := 1 ;
WHILE first[i] <> CHR(0) DO INC(i) ; END ;
WHILE second[j] <> CHR(0) DO first[i] := second[j] ; INC(j) ; INC(i) ; END ;
first[i] := CHR(0) ;
RETURN first ;
END strcat ;
(***************************************************************************)
PROCEDURE createscene ();
(***************************************************************************)
(* *)
(* subprocedures: *)
(* get_viewer, get_light, get_background, get_poly, , *)
(* get_material, Read_comment *)
(* *)
(* global variable: *)
(* *)
(* function: *)
(* read viewer, object and light-data. *)
(* If no viewer exist, then program will be terminated. If more than one *)
(* viewer exist, then the last one would be used. *)
(* The direction of light-rays will be accepted as 'at - lpos'. *)
(* *)
(* Inputfile has to be in NFF-Format : *)
(* *)
(* # Viewpoint *)
(* v from pos_x pos_y pos_z *)
(* at at_x at_y at_z *)
(* up x y z *)
(* angle alpha *)
(* hither dist *)
(* resolution x y *)
(* # Lights *)
(* l pos_x pos_y pos_z r g b *)
(* # Backgroundcolor *)
(* b r g b *)
(* # Material *)
(* r g b kdr ksr shine kst eta *)
(* # Polygones *)
(* P num.Corners x y z x y z ... *)
(* *)
(***************************************************************************)
SCALAR
view: BOOLEAN; (* true = viewer known *)
(* false = viewer still unknown *)
back: BOOLEAN; (* true = background known *)
(* false = background still unknown*)
material: BOOLEAN; (* true = surface known *)
(* false = surface still unknown *)
mat: RECORD (* current material *)
color: RGB;
kdr: REAL;
ksr: REAL;
shine: REAL;
kst: REAL;
eta: REAL;
END;
t: CHAR; (* l = light *)
(* p = polygon *)
(* v = viewer *)
PROCEDURE get_viewer();
(***************************************************************************)
(* *)
(* Calling procedure: *)
(* createscene *)
(* *)
(* global variable: *)
(* eye *)
(* *)
(* function: *)
(* to read viewerdata from file *)
(* *)
(***************************************************************************)
SCALAR
i: INTEGER; (* counter *)
next: string;
resx, resy: INTEGER;
BEGIN
IF view THEN
WriteString ("WARNING: more than one viewer specified.");
WriteLn;
WriteString (" use last one.");
WriteLn;
ELSE
view := TRUE;
END;
IF Done THEN
ReadString (next);
END;
IF STRCMP (next, "from") = 0 THEN
(* read position *)
i := 1;
WHILE Done & (i <= 3) DO
ReadReal (eye.pos[i]);
INC (i);
END (* while *);
ELSE
WriteString ("ERROR: direction of view not correct.");
HALT;
END;
IF Done THEN
ReadString (next);
END;
IF STRCMP (next, "at") = 0 THEN
(* Blickrichtung einlesen *)
i := 1;
WHILE Done & (i <= 3) DO
ReadReal (eye.at[i]);
INC (i);
END (* while *);
ELSE
WriteString ("Fehler in Blickrichtung.");
HALT;
END;
IF Done THEN
ReadString (next);
END;
IF STRCMP (next, "up") = 0 THEN
(* read direction of view *)
i := 1;
WHILE Done & (i <= 3) DO
ReadReal (eye.up[i]);
INC (i);
END (* while *);
ELSE
WriteString ("ERROR: direction of view not correct.");
HALT;
END;
IF Done THEN
ReadString (next);
END;
IF STRCMP (next, "angle") = 0 THEN
(* read viewing angle *)
ReadReal (eye.angle);
ELSE
WriteString ("ERROR: viewing angle not correct.");
HALT;
END;
IF Done THEN
ReadString (next);
END;
IF STRCMP (next, "hither") = 0 THEN
ReadReal (hither);
ELSE
WriteString ("ERROR: viewerdata not correct.");
HALT;
END;
IF Done THEN
ReadString (next);
END;
IF STRCMP (next, "resolution") = 0 THEN
ReadInt (resx);
ReadInt (resy);
ELSE
WriteString ("ERROR: viewerdata not correct.");
HALT;
END;
IF (resx <> Size) OR (resy <> Size) THEN
WriteString ("Using default imagesize for resolution.");
WriteLn;
END;
RETURN;
END get_viewer;
PROCEDURE get_light ();
(***************************************************************************)
(* *)
(* Calling procedure: *)
(* createscene *)
(* *)
(* global variable: *)
(* ll *)
(* *)
(* function: *)
(* read data of one light. *)
(* *)
(***************************************************************************)
SCALAR
i: INTEGER; (* counter *)
l: string;
BEGIN
IF NOT view THEN
WriteString ("ERROR: viewer has to be specified before lights.");
HALT;
END;
(* increase number of lights and check, if max number is not overcrossed *)
INC (lightcount);
IF (lightcount > MaxLights) THEN
WriteString ("ERROR: too much lights, maximal");
WriteInt (MaxLights,2);
HALT;
END (* if *);
(* read position *)
i := 1;
WHILE Done & (i <= 3) DO
ReadReal (ll[lightcount].pos[i]);
INC (i);
END (* while *);
ll[lightcount].pos := rot_xyz (ll[lightcount].pos);
(* read intensity of light *)
i := 1;
WHILE Done & (i <= 3) DO
ReadReal (ll[lightcount].intensity[i]);
INC (i);
END (* while *);
IF NOT Done THEN
WriteString ("ERROR: lightdata not correct.");
HALT;
END;
END get_light;
PROCEDURE get_background ();
(***************************************************************************)
(* *)
(* Calling procedure: *)
(* createscene *)
(* *)
(* global variable: *)
(* back *)
(* *)
(* function: *)
(* read background data. *)
(* *)
(***************************************************************************)
SCALAR
i: INTEGER; (* counter *)
BEGIN
IF back THEN
WriteString ("WARNING: more than one background-color specified.");
WriteLn;
WriteString (" use last one.");
WriteLn;
ELSE
back := TRUE;
END;
i := 1;
WHILE Done & (i <= 3) DO
ReadReal (background[i]);
INC (i);
END (* while *);
IF NOT Done THEN
WriteString ("ERROR: background-data not correct.");
HALT;
END;
END get_background;
PROCEDURE get_material ();
(***************************************************************************)
(* *)
(* Calling procedure: *)
(* createscene *)
(* *)
(* global variable: *)
(* *)
(* function: *)
(* read surface-data for the following object. *)
(* *)
(***************************************************************************)
BEGIN
material := TRUE;
IF Done THEN
ReadReal (mat.color[1]);
ReadReal (mat.color[2]);
ReadReal (mat.color[3]);
ReadReal (mat.kdr);
ReadReal (mat.ksr);
ReadReal (mat.shine);
ReadReal (mat.kst);
ReadReal (mat.eta);
END;
IF NOT Done THEN
WriteString ("WARNING: surface-description not correct.");
END;
END get_material;
PROCEDURE get_poly ();
(***************************************************************************)
(* *)
(* Calling procedure: *)
(* createscene *)
(* *)
(* global variable: *)
(* pl *)
(* *)
(* function: *)
(* read polygone-data *)
(* *)
(***************************************************************************)
SCALAR
i: INTEGER; (* counter *)
BEGIN
IF NOT view THEN
WriteString ("ERROR: viewer has to be specified before polygones.");
HALT;
END;
(* increase number of polygones and check, if max number is not overcrossed*)
INC (polycount);
IF (polycount > MaxPoly) THEN
WriteString ("ERROR: too much polygones, maximal");
WriteInt (MaxPoly,2);
HALT;
END (* if *);
(* read corners of polygones. *)
ReadInt (pl[polycount].vcount);
i := 1;
WHILE (i <= pl[polycount].vcount) & Done DO
ReadReal (pl[polycount].vertices[i][1]);
ReadReal (pl[polycount].vertices[i][2]);
ReadReal (pl[polycount].vertices[i][3]);
pl[polycount].vertices[i] := rot_xyz (pl[polycount].vertices[i]);
INC (i);
END (* while *);
IF NOT Done THEN
WriteString ("ERROR: polygone-data not correct.");
HALT;
END;
IF NOT material THEN
WriteString ("WARNING: no surface-description for object.");
END;
pl[polycount].color := mat.color;
END get_poly;
PROCEDURE Read_comment ();
(***************************************************************************)
(* *)
(* Calling procedure: *)
(* createscene *)
(* *)
(* function: *)
(* read line untill eoln *)
(* *)
(***************************************************************************)
SCALAR
c: CHAR;
BEGIN
REPEAT
Read (c);
UNTIL (NOT Done) OR (c = EOL);
END Read_comment;
BEGIN (* createscene *)
lightcount := 0;
polycount := 0;
view := FALSE;
back := FALSE;
material := FALSE;
OpenInput (objfile);
IF (Done) THEN
Read (t);
ELSE
WriteString ("ERROR: can't open inputfile");
HALT;
END (* if *);
WHILE (Done) DO
CASE (CAP(t)) OF
'V': get_viewer;
compute_transformation;
up := rot_xy (eye.up);;
sin_z := up[1] / Sqrt (up[1]**2 + up[2]**2);
cos_z := up[2] / Sqrt (up[1]**2 + up[2]**2);
Read_comment; |
'L': get_light;
Read_comment; |
'F': get_material;
Read_comment; |
'B': get_background;
Read_comment; |
'S': WriteString ("ERROR: can't calculate spheres.");
WriteLn;
HALT; |
'P': get_poly;
Read_comment ;|
EOL: |
'#': Read_comment;
ELSE
WriteString ("ERROR: wrong tag in inputfile.");
HALT;
END;
Read (t);
END (* while *);
IF NOT back THEN
background[1] := 0.;
background[2] := 0.;
background[3] := 0.;
END;
IF NOT view THEN
WriteString ("ERROR: no viewer specified.");
HALT;
END;
amb[1] := .035;
amb[2] := .035;
amb[3] := .035;
END createscene;
PROCEDURE compute_transformation ();
(***************************************************************************)
(* *)
(* global variable: *)
(* v, eye, sin_x, sin_y, cos_x, cos_y *)
(* *)
(* function: *)
(* Calculation of helping variables for Image-Transformation *)
(* *)
(***************************************************************************)
SCALAR
length: REAL; (* length of viewing ray *)
h: REAL;
v: Vec; (* negative viewing direction *)
lup: REAL; (* length of up-vector *)
BEGIN
(* calculation viewing ray *)
v[1] := eye.pos[1] - eye.at[1];
v[2] := eye.pos[2] - eye.at[2];
v[3] := eye.pos[3] - eye.at[3];
length := Sqrt (v[1]**2 + v[2]**2 + v[3]**2);
v[1] := v[1] / length;
v[2] := v[2] / length;
v[3] := v[3] / length;
(* Calculates dieparameter for rotation around x-, y- and z-axle *)
h := Sqrt (v[2]**2 + v[3]**2);
sin_x := v[2] / h;
cos_x := v[3] / h;
sin_y := v[1];
cos_y := h;
END compute_transformation;
PROCEDURE rot_xyz (SCALAR v1: Vec):SCALAR Vec;
(***************************************************************************)
(* *)
(* global variable: *)
(* sin_x, sin_y, sin_z, cos_x, cos_y, cos_z *)
(* *)
(* function: *)
(* Transformiert den Punkt v1 in das Sehfeld. *)
(* *)
(***************************************************************************)
SCALAR
h1, h2, v2: Vec;
BEGIN
(* rotation around x-axle *)
h1[1] := v1[1];
h1[2] := cos_x * v1[2] - sin_x * v1[3];
h1[3] := sin_x * v1[2] + cos_x * v1[3];
(* rotation around y-axle *)
h2[1] := cos_y * h1[1] - sin_y * h1[3];
h2[2] := h1[2];
h2[3] := sin_y * h1[1] + cos_y * h1[3];
(* rotation around z-axle *)
v2[1] := cos_z * h2[1] - sin_z * h2[2];
v2[2] := sin_z * h2[1] + cos_z * h2[2];
v2[3] := h2[3];
RETURN (v2);
END rot_xyz;
PROCEDURE rot_xy (SCALAR v1: Vec):SCALAR Vec;
(***************************************************************************)
(* *)
(* global variable: *)
(* sin_x, sin_y, sin_z, cos_x, cos_y, cos_z *)
(* *)
(* function: *)
(* Transformiert den Vektor v1 in das Sehfeld ohne die Richtung von 'up' *)
(* zu beruecksichtigen. *)
(* *)
(***************************************************************************)
SCALAR
h, v2: Vec;
BEGIN
(* rotation around x-axle *)
h[1] := v1[1];
h[2] := cos_x * v1[2] - sin_x * v1[3];
h[3] := sin_x * v1[2] + cos_x * v1[3];
(* rotation around y-axle *)
v2[1] := cos_y * h[1] - sin_y * h[3];
v2[2] := h[2];
v2[3] := sin_y * h[1] + cos_y * h[3];
RETURN (v2);
END rot_xy;
PROCEDURE planes ();
(***************************************************************************)
(* *)
(* global variable: *)
(* polycount,pl *)
(* Vecpoly *)
(* *)
(* function: *)
(* calculates out of the corners of a polygone the plain-coefficient of *)
(* the plain-equation : *)
(* ax + by + cz + d = 0 *)
(* *)
(***************************************************************************)
VECTOR
v1, v2: Vec; (* edgevec *)
i: INTEGER; (* counter *)
r: REAL;
dot: REAL;
BEGIN
PARALLEL [1..polycount],[1]
(* calculates out of the first three corners the plain-coefficient *)
v1[1] := Vecpoly.vertices[2][1] - Vecpoly.vertices[1][1];
v1[2] := Vecpoly.vertices[2][2] - Vecpoly.vertices[1][2];
v1[3] := Vecpoly.vertices[2][3] - Vecpoly.vertices[1][3];
v2[1] := Vecpoly.vertices[3][1] - Vecpoly.vertices[2][1];
v2[2] := Vecpoly.vertices[3][2] - Vecpoly.vertices[2][2];
v2[3] := Vecpoly.vertices[3][3] - Vecpoly.vertices[2][3];
Vecpoly.a := v1[2] * v2[3] - v1[3] * v2[2];
Vecpoly.b := v2[1] * v1[3] - v2[3] * v1[1];
Vecpoly.c := v1[1] * v2[2] - v1[2] * v2[1];
Vecpoly.d := - Vecpoly.a * Vecpoly.vertices [1][1] -
Vecpoly.b * Vecpoly.vertices [1][2] -
Vecpoly.c * Vecpoly.vertices [1][3];
(* check, if points represents a plain *)
IF (Vecpoly.a = 0.0) AND (Vecpoly.b = 0.0) AND
(Vecpoly.c = 0.0) AND (Vecpoly.d = 0.0) THEN
WriteString ("ERROR: polygonpoints represent no plain");
HALT;
END (* if *);
(* in case the area is visible, check whether all other edge points are *)
(* in the same plain *)
IF Vecpoly.c >= 0. THEN (* normal is pointing to the front *)
i := 4;
WHILE i <= Vecpoly.vcount DO
IF (ABS(Vecpoly.a*Vecpoly.vertices[i][1] +
Vecpoly.b*Vecpoly.vertices[i][2] +
Vecpoly.c*Vecpoly.vertices[i][3] +
Vecpoly.d) > .001) THEN
WriteString ("ERROR: points represent no plain");
HALT;
END (* if *);
INC (i);
END (* while *);
(* scaling normalvector and adjusting d corresponding. *)
dot := Vecpoly.a**2 + Vecpoly.b**2 +
Vecpoly.c**2;
Vecpoly.a := Vecpoly.a / Sqrt (dot);
Vecpoly.b := Vecpoly.b / Sqrt (dot);
Vecpoly.c := Vecpoly.c / Sqrt (dot);
Vecpoly.d := Vecpoly.d / Sqrt(dot);
END;
ENDPARALLEL;
END planes;
PROCEDURE shade ();
(***************************************************************************)
(* *)
(* global variable: *)
(* Vecpoly *)
(* *)
(* function: *)
(* calculate shade of area relative to incoming light. *)
(* The light fall in perpendicular to the (x,y) - plain. That mean, that *)
(* the lightvec has the coordinates (0,0,1). *)
(* *)
(***************************************************************************)
SCALAR
x: INTEGER;
i: INTEGER;
dir: Vec; (* lightvec *)
length: REAL; (* length of lightvec *)
VECTOR
color: RGB;
mult: REAL; (* cosinus of angle between *)
(* normalvec and incoming light *)
BEGIN
PARALLEL
color := Vecpoly.color;
Vecpoly.color[1] := 0.;
Vecpoly.color[2] := 0.;
Vecpoly.color[3] := 0.;
ENDPARALLEL;
FOR i := 1 TO lightcount DO
dir[1] := ll[i].pos[1] - eye.at[1];
dir[2] := ll[i].pos[2] - eye.at[2];
dir[3] := ll[i].pos[3] - eye.at[3];
length := Sqrt (dir[1]**2 + dir[2]**2 + dir[3]**2);
dir[1] := dir[1] / length;
dir[2] := dir[2] / length;
dir[3] := dir[3] / length;
PARALLEL [1..polycount],[1]
IF Vecpoly.c > 0. THEN
Vecpoly.pid := DIM1;
mult := Vecpoly.a*dir[1] + Vecpoly.b*dir[2] +
Vecpoly.c*dir[3];
IF mult > 0. THEN
Vecpoly.color[1] := Vecpoly.color[1] +
color[1] * mult;
Vecpoly.color[2] := Vecpoly.color[2] +
color[2] * mult;
Vecpoly.color[3] := Vecpoly.color[3] +
color[3] * mult;
END;
polycount := REDUCE.SUM (1);
x := polycount;
STORE (Vecpoly, pl, x);
END;
ENDPARALLEL;
END;
END shade;
PROCEDURE pixelkoord ();
(***************************************************************************)
(* *)
(* global variable: *)
(* vpixel *)
(* pg *)
(* *)
(* function: *)
(* initialize pixelarray, so that the origin of the coordinate-system *)
(* lies in the center. *)
(* *)
(***************************************************************************)
SCALAR
max: REAL; (* absolute value of *)
(* marginpoints *)
BEGIN
max := hither * Tan (eye.angle / 360. * PI);
interval := 2. * max / FLOAT (Size);
PARALLEL
(* calculate center of each pixel *)
vpixel.x := - max + (FLOAT (DIM2) - .5) *interval;
vpixel.y := max - (FLOAT (DIM1) - .5) *interval;
(* initialize color and depth *)
vpixel.color:= background;
vpixel.z := -100000.0;
ENDPARALLEL;
END pixelkoord;
PROCEDURE pixelcolor (SCALAR p:Polygon);
(***************************************************************************)
(* *)
(* global variable: *)
(* vpixel *)
(* *)
(* function: *)
(* calculates the pixel inside the polygone. *)
(* If another polygone is already visible, then the z-values would be *)
(* compared and the corresponding color and z-value of foreground polygone*)
(* would be stored. *)
(* *)
(***************************************************************************)
SCALAR
i: INTEGER;
v1,v2: Vec; (* corners of current edge *)
min,max: REAL; (* Min and Max of x-coordinates *)
(* of v1,v2 *)
VECTOR
xs: REAL; (* intersection of current *)
znew: REAL; (* z-value of pixel in new poly. *)
inpoly: BOOLEAN; (* TRUE, if pixel inside poly. *)
BEGIN
inpoly := TRUE;
FOR i := 1 TO p.vcount DO
IF i = p.vcount THEN
v2 := p.vertices[1];
ELSE
v2 := p.vertices[i+1];
END (* if *);
v1 := p.vertices[i];
IF inpoly &
((v1[2] - v2[2]) * vpixel.x + (v2[1] -v1[1]) * vpixel.y +
(v1[1] * v2[2] - v2[1] * v1[2]) < 0.) THEN
inpoly := FALSE;
END (* if *);
END (* for *);
IF inpoly THEN
IF p.c <> 0.0 THEN
znew := (-p.d - p.a*vpixel.x - p.b*vpixel.y) / p.c;
IF znew > vpixel.z THEN
vpixel.z := znew;
vpixel.color := p.color;
END (* if *);
END (* if *);
END (* if *);
END pixelcolor;
BEGIN
(* pixelcolor *)
WriteString ("Objectfile (without extension '.nff') : ");
ReadString (inputf);
picfile := strcat( inputf , ".out.ppm" );
objfile := strcat( inputf , ".nff" );
WriteLn;
WriteString (" Objectfile : " );
WriteString (objfile); WriteLn;
WriteString (" Outputfile : " );
WriteString (picfile); WriteLn; WriteLn;
createscene;
LOAD [1..polycount],[1] (Vecpoly, pl);
planes;
shade;
pixelkoord;
PARALLEL
FOR i := 1 TO polycount DO
pixelcolor (pl [i]);
END (* for *);
ENDPARALLEL;
STORE (vpixel, pg);
OpenOutput (picfile);
IF Done THEN
WriteString ("P6"); WriteLn;
WriteInt (Size,1); WriteLn;
WriteInt (Size,1); WriteLn;
WriteInt (255,1); WriteLn;
ELSE
WriteString ("ERROR: can't open inputfile");
HALT;
END;
FOR i:= 1 TO Size DO
FOR j := 1 TO Size DO
Write (CHR(TRUNC (pg[i, j].color[1] * 255.)));
Write (CHR(TRUNC (pg[i, j].color[2] * 255.)));
Write (CHR(TRUNC (pg[i, j].color[3] * 255.)));
END (* for *);
END;
CloseOutput;
END zbuff.